use std::fs;
use std::path::Path;
use std::process::Command;

use compiler::codegen::compile_to_llvm_ir;

fn compile_fixture(name: &str) -> String {
    let path = format!("tests/lab5-tests/{name}.sy");
    let source =
        fs::read_to_string(&path).unwrap_or_else(|err| panic!("读取测试文件失败 {path}: {err}"));
    compile_to_llvm_ir(&source, name).expect("生成 LLVM IR 失败")
}

fn expected_exit_code(name: &str) -> u8 {
    let exit_path = format!("tests/lab5-tests/{name}.exit");
    if Path::new(&exit_path).exists() {
        let exit_val = fs::read_to_string(&exit_path)
            .unwrap_or_else(|err| panic!("读取退出码文件失败 {exit_path}: {err}"));
        return exit_val
            .trim()
            .parse::<i32>()
            .unwrap_or_else(|err| panic!("解析退出码失败 {exit_path}: {err}"))
            .rem_euclid(256) as u8;
    }
    let expect_path = format!("tests/lab5-tests/{name}.expect");
    if Path::new(&expect_path).exists() {
        let expect_val = fs::read_to_string(&expect_path)
            .unwrap_or_else(|err| panic!("读取期望值失败 {expect_path}: {err}"));
        return expect_val
            .trim()
            .parse::<i32>()
            .unwrap_or_else(|err| panic!("解析期望值失败 {expect_path}: {err}"))
            .rem_euclid(256) as u8;
    }
    panic!("未找到 {name} 的期望文件 (.expect 或 .exit)");
}

fn run_ir_with_lli(name: &str, ir: &str) -> u8 {
    let unique = std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)
        .unwrap()
        .as_nanos();
    let tmp_path = std::env::temp_dir().join(format!("compiler_test_{name}_{unique}.ll"));
    fs::write(&tmp_path, ir)
        .unwrap_or_else(|err| panic!("写入临时 IR 文件失败 {:?}: {err}", tmp_path));
    let status = Command::new("lli")
        .arg(tmp_path.to_str().expect("临时路径包含无效 UTF-8"))
        .status()
        .unwrap_or_else(|err| panic!("执行 lli 失败: {err}"));
    let _ = std::fs::remove_file(&tmp_path);
    let code = status.code().expect("lli 运行被信号中断");
    (code & 0xff) as u8
}

fn assert_exit_matches(name: &str) {
    let expected = expected_exit_code(name);
    let ir = compile_fixture(name);
    let actual = run_ir_with_lli(name, &ir);
    assert_eq!(
        actual, expected,
        "{name} 运行结果与期望退出码不符（实际: {actual}, 期望: {expected}）"
    );
}

#[test]
fn part1_example1_constant_return() {
    assert_exit_matches("part1_example1");
}

#[test]
fn part1_example2_constant_expression() {
    assert_exit_matches("part1_example2");
}

#[test]
fn part2_example1_globals_and_locals() {
    assert_exit_matches("part2_example1");
}

#[test]
fn part2_example2_simple_local() {
    assert_exit_matches("part2_example2");
}

#[test]
fn part3_example1_function_call() {
    assert_exit_matches("part3_example1");
}

#[test]
fn part4_example1_loop_and_break() {
    assert_exit_matches("part4_example1");
}

#[test]
fn part4_example2_recursion_and_void() {
    assert_exit_matches("part4_example2");
}

#[test]
fn normaltest1_function_call_chain() {
    assert_exit_matches("normaltest1");
}

#[test]
fn normaltest2_nested_function_calls() {
    assert_exit_matches("normaltest2");
}

#[test]
fn normaltest4_local_call_assignment() {
    assert_exit_matches("normaltest4");
}

#[test]
fn normaltest9_loop_break_sum() {
    assert_exit_matches("normaltest9");
}

#[test]
fn normaltest11_multiply() {
    assert_exit_matches("normaltest11");
}

#[test]
fn debug_part1_parse_return() {
    let source =
        fs::read_to_string("tests/lab5-tests/part1_example1.sy").expect("读取 part1 示例失败");
    let program = compiler::frontend::parse_program(&source).expect("解析失败");
    assert_eq!(program.items.len(), 1, "应只有一个函数");
    match &program.items[0] {
        compiler::ast::Item::Function(func) => {
            assert_eq!(func.body.stmts.len(), 1, "函数体应只有一个语句");
            match &func.body.stmts[0] {
                compiler::ast::Stmt::Return(Some(expr)) => match expr {
                    compiler::ast::Expr::Int(value) => assert_eq!(*value, -2, "返回值应为-2"),
                    other => panic!("返回语句应为常量 -2, 实际: {:?}", other),
                },
                other => panic!("第一条语句应为 return, 实际: {:?}", other),
            }
        }
        other => panic!("顶层元素应为函数, 实际: {:?}", other),
    }
}
